package org.campuscontent.ui;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;

import org.campuscontent.config.languages.LanguageManager;
import org.campuscontent.io.AutomataEncoder;
import org.campuscontent.io.AutomataExporter;
import org.campuscontent.model.AutomataCellViewFactory;
import org.campuscontent.model.AutomataConstants.NodeType;
import org.campuscontent.ui.EdgeMaskView.AutomataEdgeEditor;
import org.campuscontent.ui.layout.LayoutConstants;
import org.campuscontent.ui.layout.LayoutManager;
import org.jgraph.event.GraphSelectionEvent;
import org.jgraph.event.GraphSelectionListener;
import org.jgraph.graph.BasicMarqueeHandler;
import org.jgraph.graph.CellView;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultPort;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphLayoutCache;
import org.jgraph.graph.GraphUndoManager;
import org.jgraph.graph.Port;
import org.jgraph.graph.PortView;

public class AutomataMarqueeHandler extends BasicMarqueeHandler implements GraphSelectionListener, KeyListener
{
    public AutomataMarqueeHandler( AutomataGraph graph )
    {
        super(); 

        this.graph = graph;

        zoom = new ZoomManager();
            
        createActions();

        // installListeners(graph);
    }

    // Display PopupMenu or Remember Start Location and First Port
    public void mousePressed( final MouseEvent e )
    {
        updateHistoryButtons();
        
        // If Right Mouse Button
        if ( SwingUtilities.isRightMouseButton( e ) )
        {
            // Find Cell in Model Coordinates
            Object cell = graph.getFirstCellForLocation( e.getX(), e.getY() );
            // Create PopupMenu for the Cell
            JPopupMenu menu = createPopupMenu( e.getPoint(), cell );
            // Display PopupMenu
            menu.show( graph, e.getX(), e.getY() );
            // Else if in ConnectMode and Remembered Port is Valid
        }
        else if ( port != null && graph.isPortsVisible() )
        {
            // Remember Start Location
            start = graph.toScreen( port.getLocation() );
            // Remember First Port
            firstPort = port;
        }
        else
        {
            // Call Superclass
            super.mousePressed( e );
        }
    }

    // Override to Gain Control (for PopupMenu and ConnectMode)
    public boolean isForceMarqueeEvent( MouseEvent e )
    {
        if ( e.isShiftDown() ) return false;
        // If Right Mouse Button we want to Display the PopupMenu
        if ( SwingUtilities.isRightMouseButton( e ) )
        // Return Immediately
        return true;
        // Find and Remember Port
        port = getSourcePortAt( e.getPoint() );
        // If Port Found and in ConnectMode (=Ports Visible)
        if ( port != null && graph.isPortsVisible() ) return true;
        // Else Call Superclass
        return super.isForceMarqueeEvent( e );
    }

    public JPopupMenu createPopupMenu( final Point pt, final Object cell )
    {
        JPopupMenu menu = new JPopupMenu();
        if ( cell != null )
        {
            JMenu edit = new JMenu( LanguageManager.str( this, "popup.edit" ) );
            menu.add( edit );
            
            // Edit
            edit.add( new AbstractAction( LanguageManager.str( this, "popup.edit.modify_label" ) )
            {
                public void actionPerformed( ActionEvent e )
                {
                    graph.startEditingAtCell( cell );
                }
            } );
            
            if ( cell instanceof DefaultGraphCell )
            {
                // Change to 'Start'
                
                DefaultGraphCell dgc = (DefaultGraphCell)cell;
                NodeType nodeType = AutomataCellViewFactory.getNodeType( dgc );

                if ( nodeType != null )
                {
                    //   add two checks
                    
                    final JCheckBoxMenuItem start = new JCheckBoxMenuItem( LanguageManager.str( this, "popup.edit.start" ), false );
                    final JCheckBoxMenuItem end   = new JCheckBoxMenuItem( LanguageManager.str( this, "popup.edit.end"   ), false );
                    
                    if ( nodeType.equals( NodeType.NORMAL ) )
                    {
                        start.setSelected( false );
                        end  .setSelected( false );
                    }
                    else if ( nodeType.equals( NodeType.STARTEND ) )
                    {
                        start.setSelected( true );
                        end  .setSelected( true );
                    }
                    else if ( nodeType.equals( NodeType.START ) )
                    {
                        start.setSelected( true );
                    }
                    else if ( nodeType.equals( NodeType.END ) )
                    {
                        end  .setSelected( true );
                    }
                    
                    edit.add( start );
                    edit.add( end   );
                    
                    start.addItemListener( new ItemListener()
                    {
                        public void itemStateChanged( ItemEvent e ) 
                        {
                            if ( start.isSelected() && end.isSelected() )
                            {
                                graph.setStartEndNode( (DefaultGraphCell)cell );
                            }
                            else if ( !start.isSelected() && !end.isSelected() )
                            {
                                graph.setNormalNode( (DefaultGraphCell)cell );
                            }
                            else if ( start.isSelected() && !end.isSelected() )
                            {
                                graph.setStartNode( (DefaultGraphCell)cell );
                            }
                            else if ( !start.isSelected() && end.isSelected() )
                            {
                                graph.setEndNode( (DefaultGraphCell)cell );
                            }
                        }
                    } );
                    
                    end.addItemListener( new ItemListener()
                    {
                        public void itemStateChanged( ItemEvent e ) 
                        {
                            if ( start.isSelected() && end.isSelected() )
                            {
                                graph.setStartEndNode( (DefaultGraphCell)cell );
                            }
                            else if ( !start.isSelected() && !end.isSelected() )
                            {
                                graph.setNormalNode( (DefaultGraphCell)cell );
                            }
                            else if ( start.isSelected() && !end.isSelected() )
                            {
                                graph.setStartNode( (DefaultGraphCell)cell );
                            }
                            else if ( !start.isSelected() && end.isSelected() )
                            {
                                graph.setEndNode( (DefaultGraphCell)cell );
                            }
                        }
                    } );
                    
                }
                else if ( cell instanceof DefaultEdge )
                {
                    final DefaultEdge de = (DefaultEdge)cell;
                    edit.add( new AbstractAction( LanguageManager.str( this, "popup.edit.invert" ) )
                    {
                        public void actionPerformed( ActionEvent e )
                        {
                            graph.invertEdge( de );
                        }
                    } );
                }
            }
        }
        // Remove
        if ( !graph.isSelectionEmpty() )
        {
            menu.addSeparator();
            menu.add( new AbstractAction( LanguageManager.str( this, "popup.remove" ) )
            {
                public void actionPerformed( ActionEvent e )
                {
                    remove.actionPerformed( e );
                }
            } );
        }
        menu.addSeparator();
        // Insert
        menu.add( new AbstractAction( LanguageManager.str( this, "popup.insert" ) )
        {
            public void actionPerformed( ActionEvent ev )
            {
                graph.insert( pt );
            }
        } );

        if ( cell != null && cell instanceof DefaultGraphCell )
        {
            menu.add( new AbstractAction( LanguageManager.str( this, "popup.selfconnection" ) )
            {
                public void actionPerformed( ActionEvent e )
                {
                    PortView portViewAt = graph.getPortViewAt( pt.x, pt.y );
                    
                    if ( portViewAt != null )
                        createSelfEdge( (Port) portViewAt.getCell() );
                }
            } );
        }
        
        menu.addSeparator();
        // Save
        menu.add( new AbstractAction( LanguageManager.str( this, "popup.save" ) )
        {
            public void actionPerformed( ActionEvent ev )
            {
                save.actionPerformed( ev );
            }
        } );

        // Load
        menu.add( new AbstractAction( LanguageManager.str( this, "popup.load" ) )
        {
            public void actionPerformed( ActionEvent ev )
            {
                load.actionPerformed( ev );
            }
        } );

        menu.addSeparator();
        
        JMenu viewChoice = new JMenu( LanguageManager.str( this, "popup.view" ) );
        menu.add( viewChoice );

        // layout
        viewChoice.add( new AbstractAction( LayoutConstants.View.FIT_IN_WINDOW )
        {
            public void actionPerformed( ActionEvent ev )
            {
                ActionEvent ae = new ActionEvent( ev.getSource(), ev.getID(), LayoutConstants.View.FIT_IN_WINDOW );
                view.actionPerformed( ae );
            }
        } );
        
        viewChoice.add( new AbstractAction( LayoutConstants.View.ZOOM_IN )
        {
            public void actionPerformed( ActionEvent ev )
            {
                ActionEvent ae = new ActionEvent( ev.getSource(), ev.getID(), LayoutConstants.View.ZOOM_IN );
                view.actionPerformed( ae );
            }
        } );
        
        viewChoice.add( new AbstractAction( LayoutConstants.View.ZOOM_OUT )
        {
            public void actionPerformed( ActionEvent ev )
            {
                ActionEvent ae = new ActionEvent( ev.getSource(), ev.getID(), LayoutConstants.View.ZOOM_OUT );
                view.actionPerformed( ae );
            }
        } );
        
        viewChoice.add( new AbstractAction( LayoutConstants.View.REAL )
        {
            public void actionPerformed( ActionEvent ev )
            {
                ActionEvent ae = new ActionEvent( ev.getSource(), ev.getID(), LayoutConstants.View.REAL );
                view.actionPerformed( ae );
            }
        } );
        
        JMenu alignChoice        = new JMenu( LanguageManager.str( this, "popup.align"        ) );
        JMenu distributionChoice = new JMenu( LanguageManager.str( this, "popup.distribution" ) );
        menu.add( alignChoice        );
        menu.add( distributionChoice );
        
        // arrangement
        // use or not equidistant function
        
        /*
        final JCheckBoxMenuItem equidistant = new JCheckBoxMenuItem( LayoutConstants.AlignType.LABEL_EQUIDISTANT_ALIGN, lm.useEquidistant() );
        arrangementChoice.add( equidistant );
        
        equidistant.addItemListener( new ItemListener()
        {
            public void itemStateChanged( ItemEvent e ) 
            {
                LayoutManager lm = LayoutManager.getInstance();
                lm.setUseEquidistant( equidistant.isSelected() );
            }
        } );
        */
        
        int size = LayoutConstants.AlignType.LABELS.length;
        for ( int idx = 0; idx < size; idx++ )
        {
            final int indice = idx;
            alignChoice.add( new AbstractAction( LayoutConstants.AlignType.LABELS[ idx ] )
            {
                public void actionPerformed( ActionEvent ev )
                {
                    ActionEvent ae = new ActionEvent( ev.getSource(), ev.getID(), LayoutConstants.AlignType.ALL[ indice ] );
                    align.actionPerformed( ae );
                }
            } );
        }
        
        size = LayoutConstants.DistributionType.LABELS.length;
        for ( int idx = 0; idx < size; idx++ )
        {
            final int indice = idx;
            distributionChoice.add( new AbstractAction( LayoutConstants.DistributionType.LABELS[ idx ] )
            {
                public void actionPerformed( ActionEvent ev )
                {
                    ActionEvent ae = new ActionEvent( ev.getSource(), ev.getID(), LayoutConstants.DistributionType.ALL[ indice ] );
                    distribution.actionPerformed( ae );
                }
            } );
        }
        
        JMenu layoutChoice = new JMenu( LanguageManager.str( this, "popup.layout" ) );
        menu.add( layoutChoice );
        
        // layout
        size = LayoutConstants.LayoutType.LABELS.length;
        for ( int idx = 0; idx < size; idx++ )
        {
            final int indice = idx;
            layoutChoice.add( new AbstractAction( LayoutConstants.LayoutType.LABELS[ idx ] )
            {
                public void actionPerformed( ActionEvent ev )
                {
                    ActionEvent ae = new ActionEvent( ev.getSource(), ev.getID(), LayoutConstants.LayoutType.ALL[ indice ] );
                    layout.actionPerformed( ae );
                }
            } );
        }
        
        menu.addSeparator();
        
        // export
        menu.add( new AbstractAction( LanguageManager.str( this, "popup.export" ) )
        {
            public void actionPerformed( ActionEvent ev )
            {
                export.actionPerformed( ev );
            }
        } );
        
        return menu;
    }

    public PortView getSourcePortAt( Point2D point )
    {
        // Disable jumping
        graph.setJumpToDefaultPort( false );
        PortView result;
        try
        {
            // Find a Port View in Model Coordinates and Remember
            result = graph.getPortViewAt( point.getX(), point.getY() );
        }
        finally
        {
            graph.setJumpToDefaultPort( true );
        }
        return result;
    }

    public PortView getSourcePortAt( double X, double Y )
    {
        // Disable jumping
        graph.setJumpToDefaultPort( false );
        PortView result;
        try
        {
            // Find a Port View in Model Coordinates and Remember
            result = graph.getPortViewAt( X, Y );
        }
        finally
        {
            graph.setJumpToDefaultPort( true );
        }
        return result;
    }
    
    // Find Port under Mouse and Repaint Connector
    public void mouseDragged( MouseEvent e )
    {
        // If remembered Start Point is Valid
        if ( start != null )
        {
            // Fetch Graphics from Graph
            Graphics g = graph.getGraphics();
            // Reset Remembered Port
            PortView newPort = getTargetPortAt( e.getPoint() );
            // Do not flicker (repaint only on real changes)
            if ( newPort == null || newPort != port )
            {
                // Xor-Paint the old Connector (Hide old Connector)
                paintConnector( Color.black, graph.getBackground(), g );
                // If Port was found then Point to Port Location
                port = newPort;
                if ( port != null ) current = graph.toScreen( port.getLocation() );
                // Else If no Port was found then Point to Mouse Location
                else current = graph.snap( e.getPoint() );
                // Xor-Paint the new Connector
                paintConnector( graph.getBackground(), Color.black, g );
            }
        }
        // Call Superclass
        super.mouseDragged( e );
    }

    // Use Xor-Mode on Graphics to Paint Connector
    protected void paintConnector( Color fg, Color bg, Graphics g )
    {
        // Set Foreground
        g.setColor( fg );
        // Set Xor-Mode Color
        g.setXORMode( bg );
        // Highlight the Current Port
        paintPort( graph.getGraphics() );
        // If Valid First Port, Start and Current Point
        if ( firstPort != null && start != null && current != null )
        // Then Draw A Line From Start to Current Point
        g.drawLine( (int) start.getX(), (int) start.getY(), (int) current.getX(), (int) current.getY() );
    }

    // Use the Preview Flag to Draw a Highlighted Port
    protected void paintPort( Graphics g )
    {
        // If Current Port is Valid
        if ( port != null )
        {
            // If Not Floating Port...
            boolean o = (GraphConstants.getOffset( port.getAllAttributes() ) != null);
            // ...Then use Parent's Bounds
            Rectangle2D r = (o) ? port.getBounds() : port.getParentView().getBounds();
            // Scale from Model to Screen
            r = graph.toScreen( (Rectangle2D) r.clone() );
            // Add Space For the Highlight Border
            r.setFrame( r.getX() - 3, r.getY() - 3, r.getWidth() + 6, r.getHeight() + 6 );
            // Paint Port in Preview (=Highlight) Mode
            graph.getUI().paintCell( g, port, r, true );
        }
    }

    // Find a Cell at point and Return its first Port as a PortView
    protected PortView getTargetPortAt( Point2D point )
    {
        // Find a Port View in Model Coordinates and Remember
        return graph.getPortViewAt( point.getX(), point.getY() );
    }

    // Show Special Cursor if Over Port
    public void mouseMoved( MouseEvent e )
    {
        // Check Mode and Find Port
        if ( e != null && getSourcePortAt( e.getPoint() ) != null && graph.isPortsVisible() )
        {
            // Set Cusor on Graph (Automatically Reset)
            graph.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
            // Consume Event
            // Note: This is to signal the BasicGraphUI's
            // MouseHandle to stop further event processing.
            e.consume();
        }
        else
        // Call Superclass
        super.mouseMoved( e );
    }

    // Connect the First Port and the Current Port in the Graph or Repaint
    public void mouseReleased( MouseEvent e )
    {
        // If Valid Event, Current and First Port
        if ( e != null && port != null && firstPort != null && firstPort != port )
        {
            // Then Establish Connection
            connect( (Port) firstPort.getCell(), (Port) port.getCell() );
            e.consume();
            // Else Repaint the Graph
        }
        else graph.repaint();
        
        // Reset Global Vars
        firstPort = port = null;
        start = current = null;
        // Call Superclass
        super.mouseReleased( e );
    }

    // Insert a new Edge between source and target
    public void connect( Port source, Port target )
    {
        // Construct Edge with no label
        DefaultEdge edge = graph.createDefaultEdge();
        GraphLayoutCache graphLayoutCache = graph.getGraphLayoutCache();
        
        if ( graph.getModel().acceptsSource( edge, source ) && graph.getModel().acceptsTarget( edge, target ) )
        {
            // Create a Map that holds the attributes for the edge
            Map attr = graph.createEdgeAttributes();
            edge.getAttributes().applyMap( attr );
            
            DefaultGraphCell sc = (DefaultGraphCell)((DefaultPort)source).getParent();
            DefaultGraphCell tg = (DefaultGraphCell)((DefaultPort)target).getParent();
            
            String scName = AutomataCellViewFactory.getName( sc );
            String tgName = AutomataCellViewFactory.getName( tg );

            Point2D[] defaultControlPoints = UIUtils.getDefaultControlPoints( 
                                             sc, 
                                             tg,
                                             edge );
            
            ArrayList points = new ArrayList<Point2D>( 3 );
            for ( Point2D pt : defaultControlPoints )
            {
                points.add( pt );
            }
            GraphConstants.setPoints( edge.getAttributes(), points );
            
            edge.setUserObject( AutomataEdgeEditor.getLinkName( scName, tgName ) );
            
            CellView mappingEdge = graphLayoutCache.getMapping( edge, false ); // EdgeView
            graphLayoutCache.update( mappingEdge );
            
            // Insert the Edge and its Attributes
            graphLayoutCache.insertEdge( edge, source, target );
        }
        else 
        {
            graph.invalidate();
            graph.validate();
            graph.repaint();
        }
    }
    
    public void connect( Port source, Port target, String label )
    {
        // Construct Edge with no label
        DefaultEdge edge = graph.createDefaultEdge();
        GraphLayoutCache graphLayoutCache = graph.getGraphLayoutCache();
        
        
        if ( graph.getModel().acceptsSource( edge, source ) && graph.getModel().acceptsTarget( edge, target ) )
        {
            // Create a Map that holds the attributes for the edge
            Map attr = graph.createEdgeAttributes();
            edge.getAttributes().applyMap( attr );
            
            DefaultGraphCell sc = (DefaultGraphCell)((DefaultPort)source).getParent();
            DefaultGraphCell tg = (DefaultGraphCell)((DefaultPort)target).getParent();
            
            Point2D[] defaultControlPoints = UIUtils.getDefaultControlPoints( 
                                             sc, 
                                             tg,
                                             edge );
            
            ArrayList points = new ArrayList<Point2D>( 3 );
            for ( Point2D pt : defaultControlPoints )
            {
                points.add( pt );
            }
            GraphConstants.setPoints( edge.getAttributes(), points );
            
            edge.setUserObject( label );
            
            CellView mappingEdge = graphLayoutCache.getMapping( edge, false ); // EdgeView
            graphLayoutCache.update( mappingEdge );
            
            // Insert the Edge and its Attributes
            graphLayoutCache.insertEdge( edge, source, target );
        }
        else 
        {
            graph.invalidate();
            graph.validate();
            graph.repaint();
        }
    }
    
    public void createSelfEdge( Port source )
    {
        DefaultEdge self = graph.createDefaultEdge();
        self.getAttributes().applyMap( graph.createEdgeAttributes() );
        graph.getGraphLayoutCache().insertEdge( self, source, source );
    }
    
    private void createActions()
    {
        remove = new AbstractAction( "" )
        {
            public void actionPerformed( ActionEvent e )
            {
                deleteSelection();
            }
        };

        save = new AbstractAction( "" )
        {
            public void actionPerformed( ActionEvent e )
            {
                AutomataEncoder encoder;

                try
                {
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    encoder = new AutomataEncoder( new BufferedOutputStream( bos ) );

                    encoder.writeObject( graph );
                    encoder.close();

                    str = bos.toString();
                }
                catch ( Exception et )
                {
                    JOptionPane.showMessageDialog( graph, et.getMessage(), "Error", JOptionPane.ERROR_MESSAGE );
                }
                finally
                {
                    graph.getParent().invalidate();
                    graph.getParent().validate();
                    graph.getParent().repaint();
                }
            }
        };

        load = new AbstractAction( "" )
        {
            public void actionPerformed( ActionEvent e )
            {
                BasicMarqueeHandler marqueeHandler = graph.getMarqueeHandler();
                
                try
                {
                    byte[] buf = str.getBytes();
                    ByteArrayInputStream bis = new ByteArrayInputStream( buf );
                    XMLDecoder decoder = new XMLDecoder( new BufferedInputStream( bis ) );

                    graph = (AutomataGraph) decoder.readObject();
                    graph.setMarqueeHandler( marqueeHandler );
                }
                catch ( Exception et )
                {
                    JOptionPane.showMessageDialog( graph, et.getMessage(), "Error", JOptionPane.ERROR_MESSAGE );
                }
            }
        };
        
        view = new AbstractAction( "" )
        {
            public void actionPerformed( ActionEvent e )
            {
                try
                {
                    String command = e.getActionCommand();
                    LayoutManager lm = LayoutManager.getInstance();

                    if ( command.equals( LayoutConstants.View.FIT_IN_WINDOW ) )
                    {
                        lm.fitViewport( graph );
                        graph.requestFocus();
                        
                        return;
                    }
                    else if ( command.equals( LayoutConstants.View.ZOOM_IN ) )
                    {
                        zoom.zoomIn();
                    }
                    else if ( command.equals( LayoutConstants.View.ZOOM_OUT ) )
                    {
                        zoom.zoomOut();
                    }
                    else if ( command.equals( LayoutConstants.View.REAL ) )
                    {
                        zoom.reset();
                    }
                    
                    graph.setScale( zoom.current() );
                }
                catch ( Exception et )
                {
                    JOptionPane.showMessageDialog( graph, et.getMessage(), "Error", JOptionPane.ERROR_MESSAGE );
                }
            }
        };

        align = new AbstractAction( "" )
        {
            public void actionPerformed( ActionEvent e )
            {
                try
                {
                    String arrangementType = e.getActionCommand();
                    LayoutManager lm = LayoutManager.getInstance();
                    lm.setUseEquidistant( false );
                    lm.setFacadeTo( graph );
                    lm.runExtremeAlign( arrangementType );
                }
                catch ( Exception et )
                {
                    JOptionPane.showMessageDialog( graph, et.getMessage(), "Error", JOptionPane.ERROR_MESSAGE );
                }
            }
        };
        
        distribution = new AbstractAction( "" )
        {
            public void actionPerformed( ActionEvent e )
            {
                try
                {
                    String arrangementType = e.getActionCommand();
                    LayoutManager lm = LayoutManager.getInstance();
                    
                    lm.setUseEquidistant( true );
                    lm.setFacadeTo( graph );
                    lm.runExtremeAlign( arrangementType );
                }
                catch ( Exception et )
                {
                    JOptionPane.showMessageDialog( graph, et.getMessage(), "Error", JOptionPane.ERROR_MESSAGE );
                }
            }
        };
        
        layout = new AbstractAction( "" )
        {
            public void actionPerformed( ActionEvent e )
            {
                try
                {
                    String layoutType = e.getActionCommand();
                    LayoutManager lm = LayoutManager.getInstance();
                    
                    lm.setFacadeTo( graph );
                    lm.runLayout( layoutType );
                }
                catch ( Exception et )
                {
                    JOptionPane.showMessageDialog( graph, et.getMessage(), "Error", JOptionPane.ERROR_MESSAGE );
                }
            }
        };
        
        export = new AbstractAction( "" )
        {
            public void actionPerformed( ActionEvent e )
            {
                try
                {
                    String fn = JOptionPane.showInputDialog( "Save graph as: ", "" );
                    
                    if ( fn != null && !fn.equals( "" ) )
                    {
                        String filename = fn;
                        AutomataExporter exporter = AutomataExporter.getInstance();
                        exporter.saveAutomataGraph( graph, filename );
                    }
                }
                catch ( Exception et )
                {
                    JOptionPane.showMessageDialog( graph, et.getMessage(), "Error", JOptionPane.ERROR_MESSAGE );
                }
            }
        };
    }
    
    protected void smartDelete()
    {
        Object[] selection = UIUtils.getFullCellsOfCurrentSelection( graph );
        if ( selection != null && selection.length != 0 )
        {
            graph.getModel().remove( selection );
        }
    }

    private void deleteSelection()
    {
        if ( !graph.isSelectionEmpty() )
        {
//            Object[] cells = graph.getSelectionCells();
//            cells = graph.getDescendants( cells );
//            graph.getModel().remove( cells );
            
            smartDelete();
        }
    }

    protected void initFileChooser()
    {
        // fileChooser = new JFileChooser();
    }
    
    // Undo the last Change to the Model or the View
    public void undo() 
    {
        try 
        {
            GraphUndoManager undoManager = graph.getUndoManager();
            undoManager.undo( graph.getGraphLayoutCache() );
        } 
        catch ( Exception ex ) 
        {
            System.err.println( ex );
        } 
        finally 
        {
            updateHistoryButtons();
        }
    }

    // Redo the last Change to the Model or the View
    public void redo() 
    {
        try 
        {
            GraphUndoManager undoManager = graph.getUndoManager();
            undoManager.redo( graph.getGraphLayoutCache() );
        } 
        catch ( Exception ex ) 
        {
            System.err.println( ex );
        } 
        finally 
        {
            updateHistoryButtons();
        }
    }

    // Update Undo/Redo Button State based on Undo Manager
    protected void updateHistoryButtons() 
    {
        AutomatToolbar toolBar = graph.getToolBar();
        GraphUndoManager undoManager = graph.getUndoManager();
        toolBar.updateUndoRedo( undoManager.canUndo(graph.getGraphLayoutCache()), undoManager.canRedo(graph.getGraphLayoutCache()) );
    }
    
    public void keyReleased( KeyEvent e ) 
    {
    }

    public void keyTyped( KeyEvent e ) 
    {
    }
    
    public void keyPressed( KeyEvent e ) 
    {
        if ( e.getKeyCode() == KeyEvent.VK_DELETE )
            deleteSelection();
    }
    
    // From GraphSelectionListener Interface
    public void valueChanged( GraphSelectionEvent e ) 
    {
        AutomataGraph ag = (AutomataGraph)e.getSource();
        AutomatToolbar toolBar = ag.getToolBar();
            
        boolean nodes = false;
        boolean links = false;
        
        Object[] selectionCells = ag.getSelectionCells();
        
        for ( Object o : selectionCells )
        {
            if ( o instanceof DefaultEdge )
            {
                links = true;
                continue;
            }
            
            if ( o instanceof DefaultGraphCell )
            {
                nodes = true;
                continue;
            }
        }
        
        toolBar.updateDeleteNodes( nodes );
        toolBar.updateDeleteLinks( links );
        
        toolBar.updateZoom( zoom.canZoomOut(), zoom.canZoomIn() );
        
    }

    // Holds the Start and the Current Point
    protected Point2D start, current;

    // Holds the First and the Current Port
    protected PortView port, firstPort;

    // Actions which Change State
    public Action undo, redo, remove, group, ungroup, tofront, toback, cut, copy, paste, save, load, align, distribution, layout, view, export;

    private AutomataGraph graph;
    //private JFileChooser fileChooser;

    private String str;
    
    private ZoomManager zoom;
}

